<script lang="ts" setup>
import { isDate } from '@/utils/typeof'

/**
 * slot: 插槽
 *       actions 顶部右侧插槽（toggleButton左侧）
 *       title 标题插槽
 *       default 日历项内容插槽（mode为month）
 *          -> (date, data) 返回当前日期和事件(data | null)
 *       popover-content 日历事件悬浮提示框内容（当日历事件isError为true时有效）
 *          -> data 返回日历事件data
 *       mask-content 遮住层内容 (showMask=true有效)
 *       week-event 日历事件插槽 (周模式下)
 *          -> data 事件data
 */
export interface CalendarEvent {
  day?: string | Date
  time?: string | Date
  name?: string
  isError?: boolean
  room?: string // 教室 mode为day时
  start?: string // 开始时间 mode为day时
  end?: string // 结束时间 mode为day时
  extra: {
    [propName: string]: any
  }
}

interface Props {
  showToggle?: boolean // 是否显示切换按钮
  mode?: string // 当前模式
  selectMode?: 'button' | 'select' // 时间选择模式button：左右按钮切换 select：下拉选择切换
  showToday?: boolean // 是否显示今天按钮
  disabled?: boolean // 是否可点击
  activeDate?: Date | null // 默认选中的时间（mode为month有效）
  showMask?: boolean // 显示遮住
  events: Array<CalendarEvent>// 日历事件
  showDot?: boolean // 是否显示原点
  weekTimeSlots?: Array<string> // 周时间段数据
  showDayButton?: boolean // 是否显示日按钮
  dayRooms?: Array<string> // 模式为day时，教室
  grayBgTitle?: boolean // 灰色标题
  dateAlign: 'left' | 'right'
}

export interface CalendarItem {
  date: Date
  dateString: string
}

const props = withDefaults(defineProps<Props>(), {
  showToggle: true,
  mode: 'month',
  selectMode: 'button',
  showToday: true,
  disabled: false,
  activeDate: null,
  showMask: false,
  events: () => [],
  showDot: true,
  showDayButton: false,
  grayBgTitle: false,
  dayRooms: () => [],
  dateAlign: 'left',
})

const emits = defineEmits(['monthChange', 'modeChange', 'weekChange', 'dayChange'])

// 时间段
const timeSlots = ref<string []>()

const currentDate = ref(new Date())
const activeDate = ref()
const view = ref('month')
const yearOptions = getYearsSince(2000)
const monthOptions = Array.from({ length: 12 }, (_, i) => ({ label: `${i + 1}月`, value: i }))
const curYear = ref(new Date().getFullYear())
const curMonth = ref(new Date().getMonth())
const weekDays = ref(getWeekDays())

onMounted(() => {
  view.value = props.mode
  activeDate.value = props.activeDate
})

watch(() => props.weekTimeSlots, (val: string []) => {
  timeSlots.value = val
}, { immediate: true })

function getYearsSince(year) {
  const currentYear = new Date().getFullYear() + 1
  const years: Array<{ label: string, value: string | number }> = []
  for (let i = year; i <= currentYear; i++) {
    years.push({ label: `${i}年`, value: i })
  }
  return years
}

const modeChange = (val) => {
  if (val === 'month' || val === 'day') {
    emits('modeChange', val, currentDate.value)
  } else {
    emits('modeChange', val, weekDays.value)
  }
  setView(val)
}

const isCurrentMonth = (date) => {
  return date.getMonth() === currentDate.value.getMonth()
}

const isToday = (date2) => {
  const date1 = new Date()
  return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate()
}

const isActiveToday = (date2) => {
  if (!activeDate.value) {
    return false
  }
  const date1 = activeDate.value
  return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate()
}

// 获取一周的日期
function getWeekDays() {
  const startOfWeek = new Date(currentDate.value)
  startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay()) // 从周日开始
  const days: Array<CalendarItem> = []
  for (let i = 0; i < 7; i++) {
    const day = new Date(startOfWeek)
    day.setDate(startOfWeek.getDate() + i)
    days.push({
      date: day,
      dateString: `${day.getMonth() + 1}月${day.getDate()}日`,
    })
  }
  return days
}

// 获取当月的所有日期，按照周进行分组
function getMonthDays() {
  const days: Array< Array<CalendarItem>> = []
  const startOfMonth = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth(), 1)

  const current = new Date(startOfMonth)
  current.setDate(current.getDate() - current.getDay()) // 将日期调整到上一个周日
  let count = 1
  while (count < 7) {
    const week: Array<{ date: Date, dateString: string }> = []
    for (let i = 0; i < 7; i++) {
      week.push({
        date: new Date(current),
        dateString: `${current.getFullYear()}-${String(current.getMonth() + 1).padStart(2, '0')}-${String(
          current.getDate(),
        ).padStart(2, '0')}`,
      })
      current.setDate(current.getDate() + 1)
    }
    count++
    days.push(week)
  }
  return days
}

const monthDays = ref(getMonthDays())
const dayNames = ref(['周日', '周一', '周二', '周三', '周四', '周五', '周六'])

// 切换到今天
function goToday(date: string = '') {
  currentDate.value = date ? new Date(date) : new Date()
  curYear.value = currentDate.value.getFullYear()
  curMonth.value = currentDate.value.getMonth()
  weekDays.value = getWeekDays()
  monthDays.value = getMonthDays()
  if (view.value === 'month') {
    emits('monthChange', currentDate.value)
  } else if (view.value === 'week') {
    emits('weekChange', weekDays.value)
  } else {
    emits('dayChange', currentDate.value)
  }
}

// 切换到上一周
function previousWeek() {
  currentDate.value.setDate(currentDate.value.getDate() - 7)
  weekDays.value = getWeekDays()
  emits('weekChange', weekDays.value)
}
// 下一周
function nextWeek() {
  currentDate.value.setDate(currentDate.value.getDate() + 7)
  weekDays.value = getWeekDays()
  emits('weekChange', weekDays.value)
}

// 前一天
function previousDay() {
  const str = `${currentDate.value.getFullYear()}-${currentDate.value.getMonth() + 1}-${currentDate.value.getDate() - 1}`
  goToday(str)
}
// 后一天
function nextDay() {
  const str = `${currentDate.value.getFullYear()}-${currentDate.value.getMonth() + 1}-${currentDate.value.getDate() + 1}`
  goToday(str)
}

// 切换到上一月
function previousMonth() {
  currentDate.value.setMonth(currentDate.value.getMonth() - 1)
  emits('monthChange', currentDate.value)
  monthDays.value = getMonthDays()
}
// 下一月
function nextMonth() {
  currentDate.value.setMonth(currentDate.value.getMonth() + 1)
  emits('monthChange', currentDate.value)
  monthDays.value = getMonthDays()
}

function setMonth(month) {
  currentDate.value.setMonth(month)
  emits('monthChange', currentDate.value)
  monthDays.value = getMonthDays()
}

function setYear(year) {
  currentDate.value.setFullYear(year)
  emits('monthChange', currentDate.value)
  monthDays.value = getMonthDays()
}

// 切换视图
function setView(newView) {
  view.value = newView
}

// 判断是否有事件
function hasEvent(day, timeSlot): boolean {
  return props.events.some(
    event =>
      event.day === formatDate(day.date) && (timeSlot === '' || event.time === timeSlot),
  )
}

const getTimeInMinutes = (time?: string) => {
  if (!time) {
    return 0
  }
  // 将时间转换为分钟数
  const [hours, minutes] = time.split(':').map(Number)
  return hours * 60 + minutes
}

const getDateSpan = (event: CalendarEvent) => {
  const eventStartTime = getTimeInMinutes(event.start)
  const eventEndTime = getTimeInMinutes(event.end)
  return (eventEndTime - eventStartTime) / 60
}

const getEventStyle = (event: CalendarEvent, slot: string) => {
  const slotStartTime = getTimeInMinutes(slot)
  const eventStartTime = getTimeInMinutes(event.start)
  const eventEndTime = getTimeInMinutes(event.end)
  const startOffset = (eventStartTime - slotStartTime) / 60 * 60 // 开始时间偏移
  const count = getDateSpan(event)
  const eventDuration = ((eventEndTime - eventStartTime) / 60 + (count < 1 ? 0 : 1)) * 60 // 事件持续时间对应的高度
  return {
    top: `${startOffset + 4}px`,
    height: `${eventDuration - 8}px`,
  }
}

// 格式化日期
function formatDate(date) {
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}

// 比较日期是否相等
const compareDate = (date1: string | Date | undefined, date2: Date): boolean => {
  if (!date1) {
    return false
  }
  if (isDate(date1)) {
    return formatDate(date1) === formatDate(date2)
  } else {
    return date1 === formatDate(date2)
  }
}

// 获取事件信息
function getEvent(day, timeSlot): CalendarEvent | null {
  if (view.value !== 'day') {
    return (
      props.events.find(
        (event: CalendarEvent) =>
          compareDate(event.day, day.date) && (timeSlot === '' || (event as CalendarEvent).time === timeSlot),
      ) || null
    )
  } else {
    return (props.events as any).find((event: CalendarEvent) => {
      const eventStartTime = getTimeInMinutes(event.start)
      const slotStartTime = getTimeInMinutes(day)
      const slotEndTime = slotStartTime + 60
      return event.room === timeSlot && eventStartTime >= slotStartTime && eventStartTime < slotEndTime
    })
  }
}

// 获取当前标题
function getTitle() {
  if (view.value === 'week') {
    const month = currentDate.value.getMonth() + 1
    const weekNumber = Math.ceil(currentDate.value.getDate() / 7)
    return `${month}月第${weekNumber}周`
  } else if (view.value === 'month') {
    const year = currentDate.value.getFullYear()
    const month = String(currentDate.value.getMonth() + 1).padStart(2, '0')
    return `${year}年${month}月`
  } else {
    const year = currentDate.value.getFullYear()
    const month = String(currentDate.value.getMonth() + 1).padStart(2, '0')
    const day = String(currentDate.value.getDate()).padStart(2, '0')
    return `${year}年${month}月${day}日`
  }
}

const clickItem = (date) => {
  if (props.disabled) {
    return
  }
  if (date.getMonth() > currentDate.value.getMonth()) {
    nextMonth()
  } else if (date.getMonth() < currentDate.value.getMonth()) {
    previousMonth()
  }
  activeDate.value = date
}

defineExpose({
  goToday,
})
</script>

<template>
  <div class="calendar">
    <!-- 顶部功能按钮，上一月、下一月、今天 -->
    <div class="calendar-header gap-3">
      <div v-if="dateAlign === 'right'" class="flex-1">
        <slot name="title" />
      </div>
      <div v-if="view === 'month'" class="btn-group">
        <template v-if="selectMode === 'button'">
          <div class="icon-button" @click="previousMonth">
            <icon-left />
          </div>
          <span class="calendar-title">{{ getTitle() }}</span>
          <div class="icon-button" @click="nextMonth">
            <icon-right />
          </div>
        </template>
        <template v-else>
          <a-space>
            <a-select v-model="curYear" :options="yearOptions" style="padding: 0 10px 0 0; width: 80px;" placeholder="年份" :bordered="false" @change="setYear" />
            <a-select v-model="curMonth" :options="monthOptions" style="padding: 0 10px 0 0;width: 60px;" placeholder="月份" :bordered="false" @change="setMonth" />
          </a-space>
        </template>
        <div v-if="props.showToday" class="today-btn" @click="goToday()">
          今天
        </div>
      </div>
      <div v-else-if="view === 'week'" class="btn-group">
        <div class="icon-button" @click="previousWeek">
          <icon-left />
        </div>
        <span class="calendar-title">{{ getTitle() }}</span>
        <div class="icon-button" @click="nextWeek">
          <icon-right />
        </div>
        <div v-if="props.showToday" class="today-btn" @click="goToday()">
          今天
        </div>
      </div>
      <div v-else class="btn-group">
        <div class="icon-button" @click="previousDay">
          <icon-left />
        </div>
        <span class="calendar-title">{{ getTitle() }}</span>
        <div class="icon-button" @click="nextDay">
          <icon-right />
        </div>
        <div v-if="props.showToday" class="today-btn" @click="goToday()">
          今天
        </div>
      </div>
      <div v-if="dateAlign === 'left'" class="flex-1">
        <slot name="title" />
      </div>
      <div class="flex">
        <slot name="actions" />
        <div v-if="props.showToggle" class="flex flex-shrink-0 items-center">
          <a-radio-group v-model="view" type="button" size="small" @change="modeChange">
            <a-radio value="month">
              月
            </a-radio>
            <a-radio value="week">
              周
            </a-radio>
            <a-radio v-if="props.showDayButton" value="day">
              日
            </a-radio>
          </a-radio-group>
        </div>
      </div>
    </div>

    <!-- 周视图表格 -->
    <table v-if="view === 'week'" class="calendar-table">
      <colgroup>
        <col style="width: 88px;"> <!-- 第一列的宽度设置为88px -->
      </colgroup>
      <thead>
        <tr>
          <th />
          <th v-for="(day, index) in weekDays" :key="index" :class="{ 'gray-bg': props.grayBgTitle }">
            {{ dayNames[index] }} {{ day.dateString }}
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="timeSlot in timeSlots" :key="timeSlot">
          <td class="timeline" :class="{ 'gray-bg': props.grayBgTitle }">
            <div>
              {{ timeSlot.split('-')[0] }}
            </div>
            <div v-if="timeSlot.split('-')[1]">
              -
            </div>
            <div v-if="timeSlot.split('-')[1]">
              {{ timeSlot.split('-')[1] }}
            </div>
          </td>
          <td v-for="(day, index) in weekDays" :key="index">
            <div v-if="hasEvent(day, timeSlot)" class="calendar-cell-item pos-relative flex flex-col justify-center">
              <slot name="week-event" :data="getEvent(day, timeSlot)">
                <div class="event" :class="{ 'dot': props.showDot, 'dot error-event': (getEvent(day, '') as CalendarEvent)?.isError }">
                  <div>{{ (getEvent(day, timeSlot) as CalendarEvent)?.name }}</div>
                  <div class="mt-1">
                    {{ (getEvent(day, timeSlot) as CalendarEvent)?.time }}
                  </div>
                </div>
              </slot>
            </div>
            <div v-else class="calendar-cell-item" />
          </td>
        </tr>
      </tbody>
    </table>

    <!-- 月视图表格 -->
    <table v-else-if="view === 'month'" class="calendar-table month">
      <thead>
        <tr>
          <th v-for="dayName in dayNames" :key="dayName" :class="{ 'gray-bg': props.grayBgTitle }">
            {{ dayName }}
          </th>
        </tr>
      </thead>
      <tbody class="position-relative">
        <tr v-for="week in monthDays" :key="week[0].dateString">
          <td v-for="day in week" :key="day.dateString">
            <div class="calendar-cell-item pos-relative" @click="clickItem(day.date)">
              <slot :date="day.date" :data="getEvent(day, '')">
                <div v-if="!$slots['month-event'] || !hasEvent(day, '')" class="date" :class="{ 'is-today': isToday(day.date), 'active': isActiveToday(day.date) || hasEvent(day, ''), 'disabled': !isCurrentMonth(day.date), 'is-error': (getEvent(day, '') as CalendarEvent)?.isError }">
                  {{
                    day.date.getDate() }}
                </div>
                <!-- 显示当月事件 -->
                <div v-if="hasEvent(day, '')" :class="{ 'event': !$slots['month-event'], 'dot': props.showDot, 'dot error-event': (getEvent(day, '') as CalendarEvent)?.isError }">
                  <slot name="month-event" :date="day.date" :data="getEvent(day, '')">
                    <a-popover v-if="(getEvent(day, '') as CalendarEvent)?.isError" position="right">
                      <span style="cursor: pointer;">{{ (getEvent(day, '') as CalendarEvent)?.name }}</span>
                      <template #content>
                        <slot name="popover-content" :data="getEvent(day, '')">
                          <span>提示内容</span>
                        </slot>
                      </template>
                    </a-popover>
                    <span v-else>{{ (getEvent(day, '') as CalendarEvent)?.name }}</span>
                  </slot>
                </div>
              </slot>
            </div>
          </td>
        </tr>
        <div v-if="props.showMask" class="mask">
          <slot name="mask-content" />
        </div>
      </tbody>
    </table>

    <!-- 日视图表格 -->
    <div v-else class="calendar-container">
      <table class="calendar">
        <thead v-if="props.dayRooms.length">
          <tr>
            <th />
            <th v-for="(room, i) in props.dayRooms" :key="i" :class="{ 'gray-bg': props.grayBgTitle }">
              {{ room }}
            </th>
          </tr>
        </thead>
        <tbody v-if="props.dayRooms.length">
          <tr v-for="(slot, index) in timeSlots" :key="index">
            <td class="label fixed-left" :class="{ 'gray-bg': props.grayBgTitle }">
              <div>{{ slot }}</div>
            </td>
            <td v-for="(room, i) in props.dayRooms" :key="i">
              <div
                v-if="getEvent(slot, room)" class="event"
                :style="getEventStyle(getEvent(slot, room) as CalendarEvent, slot)"
              >
                <slot
                  name="day-event" :data="getEvent(slot, room)"
                  :range="getDateSpan(getEvent(slot, room) as CalendarEvent)"
                >
                  <span class="event-title">{{ (getEvent(slot, room) as CalendarEvent).extra?.title }}</span>
                  <span v-if="getDateSpan(getEvent(slot, room) as CalendarEvent) >= 1">{{ (getEvent(slot, room) as CalendarEvent)?.extra.teacher
                  }}</span>
                  <span v-if="getDateSpan(getEvent(slot, room) as CalendarEvent) >= 1">{{ (getEvent(slot, room) as CalendarEvent)?.start }} ~
                    {{ (getEvent(slot, room) as CalendarEvent)?.end }}</span>
                </slot>
              </div>
            </td>
          </tr>
        </tbody>
        <div v-else class="py-4 text-center">
          暂无数据
        </div>
      </table>
    </div>
  </div>
</template>

<style lang="less" scoped>
p {
  margin: 5px 0;
}
:deep(.arco-select-view-suffix) {
  padding-left: 0;
}

.calendar {
  height: 100%;
  border: 1px solid #e5e6ea;
  box-sizing: border-box;
  overflow: hidden;
}

.calendar-header {
  display: flex;
  justify-content: space-between;
  padding: 24px 18px;

  .btn-group {
    display: flex;
    align-items: center;
    user-select: none;
    .today-btn {
      margin-left: 10px;
      width: 60px;
      height: 28px;
      border-radius: 2px;
      background: #f2f3f5;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;

      &:hover {
        background: #e5e6eb;
      }
    }
    .icon-button {
      width: 28px;
      height: 28px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      color: #505968;
      &:hover {
        background: #e5e6eb;
      }
    }
  }
}

.calendar-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  .mask {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    top: 0;
    background: rgba(0, 0, 0, 0.6);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  thead {
    th {
      border: none;
      text-align: left;
      font-size: 14px;
      font-weight: 400;
      color: #1d2129;
      padding: 10px 0;
      padding-left: 12px;
    }
  }

  tr {
    td {
      &:first-child {
        border-left: none;
      }

      &:last-child {
        border-right: none;
      }
    }
  }

  tbody {
    .timeline {
      width: 88px;
    }

    tr:last-child {
      td {
        border-bottom: none;
      }
    }
  }

  .calendar-cell-item {
    min-height: 80px;

    .date {
      width: 24px;
      height: 24px;
      left: 0;
      top: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      box-sizing: border-box;
      border-radius: 50%;
      font-size: 12px;
    }

    .is-today {
      border: 1px solid #165dff;
    }

    .is-error {
      background: #f53f3f !important;
      color: #fff;
    }

    .disabled {
      color: #ccc;
    }

    .active {
      background: #165dff;
      color: #fff;
    }
  }
}

.calendar-table th,
.calendar-table td {
  border: 1px solid #e5e6ea;
  text-align: center;
  flex-shrink: 0;
  padding: 5px;
  box-sizing: border-box;
  overflow: hidden;
}

.event {
  position: relative;
  font-size: 12px;
  text-align: left;
  padding: 5px 0;
  padding-left: 14px;
  height: 100%;
  box-sizing: border-box;
  p {
    text-align: left;
    font-size: 12px;
  }
}
.dot {
  &::before {
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    content: '';
    width: 6px;
    height: 6px;
    background: #50b142;
    border-radius: 50%;
  }
}

.gray-bg {
  background-color: #f7f8fa;
}

.error-event {
  &::before {
    background: #ff7d00 !important;
  }
}

.calendar-title {
  text-align: center;
  font-size: 18px;
  margin: 0 10px;
  color: #111;
}

.calendar-container {
  table {
    width: 100%;
    border-collapse: collapse;
  }

  .label {
    width: 80px;
  }

  .fixed-left {
    position: sticky;
    left: 0;
    z-index: 1;
  }

  th,
  td {
    border: 1px solid #ddd;
    padding: 10px;
    text-align: center;
  }

  thead th {
    font-weight: normal;
    font-size: 14px;
  }

  tbody td {
    position: relative;
    height: 60px;
    /* 每个时间段的基础高度 */
    padding: 0;
    min-width: 60px;
  }

  tbody td:not(:first-child) {
    min-width: 150px;
  }

  .event {
    position: absolute;
    left: 4px;
    right: 4px;
    background-color: #e8f3ff;
    border-radius: 5px;
    font-size: 14px;
    text-align: left;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 0 10px;
  }

  .event .event-title {
    font-size: 13px;
    color: #000;
  }

  .event span {
    display: block;
    font-size: 12px;
    color: #555;
  }

  .event.long {
    height: 200px;
  }
}
</style>
